package top.z.template.parse.ast.expression;

import top.z.template.Context;
import top.z.template.Error;
import top.z.template.Template;
import top.z.template.parse.Ast;
import top.z.template.parse.Token;
import top.z.template.parse.TokenType;

import java.io.IOException;
import java.io.OutputStream;

/**
 * 一元运算, 逻辑运算或数值否定
 */
public class UnaryOperation extends Ast.Expression {

    public enum UnaryOperator {
        Not,      // 否
        Positive, // 正
        Negative; // 反

        public static UnaryOperator getOperator(Token token) {
            if (token.getType() == TokenType.Not) {
                return UnaryOperator.Not;
            }
            if (token.getType() == TokenType.Plus) {
                return UnaryOperator.Positive;
            }
            if (token.getType() == TokenType.Minus) {
                return UnaryOperator.Negative;
            }
            throw Error.error("Unknown unary operator " + token + ".", token.getSpan());
        }
    }

    private final UnaryOperator  operator;
    private final Ast.Expression operand;

    public UnaryOperation(Token operator, Ast.Expression operand) {
        super(operator.getSpan());
        this.operator = UnaryOperator.getOperator(operator);
        this.operand = operand;
    }

    public UnaryOperator getOperator() {
        return operator;
    }

    public Ast.Expression getOperand() {
        return operand;
    }

    @Override
    public Object evaluate(Template template, Context context, OutputStream out) throws IOException {
        Object operand = getOperand().evaluate(template, context, out);

        if (getOperator() == UnaryOperator.Negative) {
            if (operand instanceof Byte) {
                return -(Byte) operand;
            }
            if (operand instanceof Long) {
                return -(Long) operand;
            }
            if (operand instanceof Short) {
                return -(Short) operand;
            }
            if (operand instanceof Float) {
                return -(Float) operand;
            }
            if (operand instanceof Double) {
                return -(Double) operand;
            }
            if (operand instanceof Integer) {
                return -(Integer) operand;
            }

            throw Error.error("Operand of operator '" + getOperator().name() + "' must be a number, got " + operand, getSpan());
        } else if (getOperator() == UnaryOperator.Not) {
            if (operand instanceof Boolean) {
                return !(Boolean) operand;
            } else {
                throw Error.error("Operand of operator '" + getOperator().name() + "' must be a boolean", getSpan());
            }
        } else {
            return operand;
        }
    }
}